<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>就决定是你了，百变怪！</title>
  <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"
  />
  <style>
    * {
      box-sizing: border-box;
    }
    :root {
      --size: 60;
      --margin: 20;
      --pokeball-size: 50;
      --pokeball-white: #e6e6e6;
      --pokeball-red: #f20d0d;
      --beam-color: #f20d59;
    }
    @media (max-width: 600px) {
      :root {
        --size: 85;
      }
    }
    body {
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      background: #d1ecfa;
    }
    .ditto {
      --hue: 320;
      --lightness: 75;
      --stroke: 5;
      height: calc(var(--size) * 1vmin);
      width: calc(var(--size) * 1vmin);
      z-index: 2;
    }
    .ditto > g {
      transform: translate(200px, 200px);
    }
    .ditto g {
      fill: hsla(
              var(--hue),
              100%,
              calc(var(--lightness) * 1%),
              var(--alpha, 1)
      );
      stroke: #000;
      stroke-width: calc(var(--stroke) * 1px);
      transition: d 0.15s ease;
    }
    .ditto path {
      transition: d 0.15s ease;
    }
    .ditto__real {
      opacity: 0;
    }
    .ditto__body {
      --lightness: 45;
      --stroke: 0;
    }
    .ditto__clone {
      --lightness: 80;
      --stroke: 0;
    }
    .ditto__stroke {
      --hue: 0;
      --alpha: 0;
      --stroke: 5;
      --lightness: 0;
      fill: none;
    }
    .ditto__outline {
      --hue: 340;
      --lightness: 50;
      --stroke: 0;
    }
    .pokeball {
      --level: 50;
      height: 100%;
      width: 100%;
      border-radius: 50%;
      border: 2px solid #000;
      position: relative;
      overflow: hidden;
      transition: all 0.15s ease;
      cursor: pointer;
      background: var(--pokeball-white);
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      outline: transparent;
    }
    .pokeball:after {
      content: '';
      position: absolute;
      top: 25%;
      left: 75%;
      background: rgba(255, 255, 255, 0.5);
      border-radius: 50%;
      height: 10px;
      width: 10px;
      transform: translate(-25%, -25%) rotate(-20deg);
      z-index: 2;
    }
    .pokeball:before {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      height: 80%;
      width: 80%;
      border: 2px solid #595959;
      z-index: 2;
      border-radius: 50%;
      transform: translate(-50%, -50%);
      -webkit-clip-path: polygon(50% 50%, 100% 65%, 100% 100%, 65% 100%);
      clip-path: polygon(50% 50%, 100% 65%, 100% 100%, 65% 100%);
    }
    .pokeball__beam {
      position: absolute;
      bottom: 50%;
      left: 50%;
      transform: translate(-50%, 0);
      width: calc(var(--pokeball-size) * 0.25px);
      background: var(--beam-color);
      filter: blur(2px);
      height: calc(
              (var(--size) * 0.22vmin) + (var(--margin) * 1vmin) +
              (var(--pokeball-size) * 0.5px)
      );
      z-index: -1;
    }
    .pokeball__wrapper {
      transform-style: preserve-3d;
      height: calc(var(--pokeball-size) * 1px);
      width: calc(var(--pokeball-size) * 1px);
      position: relative;
      margin-top: calc(var(--margin) * 1vmin);
    }
    .pokeball:hover {
      --level: 0;
    }
    .pokeball__face {
      height: 100%;
      background: linear-gradient(
              var(--pokeball-red) calc(50% - 1px),
              #000 calc(50% - 1px) calc(50% + 1px),
              var(--pokeball-white) calc(50% + 1px)
      );
      width: 100%;
      position: absolute;
      top: calc(var(--level) * 1%);
      left: 0;
      transition: all 0.15s ease;
      transform: translate(0, -50%);
    }
    .pokeball__face:after {
      content: '';
      height: 5px;
      width: 5px;
      border: 2px solid #000;
      background: #fff;
      border-radius: 50%;
      position: absolute;
      top: calc(var(--level) * 1%);
      left: 50%;
      transform: translate(-50%, -50%);
    }
    .pokeball__face:before {
      content: '';
      height: 12px;
      width: 12px;
      border: 2px solid #000;
      border-radius: 50%;
      background: #bfbfbf;
      position: absolute;
      top: calc(var(--level) * 1%);
      left: 50%;
      transition: all 0.15s ease;
      transform: translate(-50%, -50%);
    }
  </style>
</head>
<body>
<svg class="ditto" viewBox="0 0 400 400">
  <defs>
    <path class="ditto__path" id="ditto__path"></path>
    <clipPath id="ditto__clip">
      <use xlink:href="#ditto__path"></use>
    </clipPath>
  </defs>
  <g class="ditto__real">
    <g class="ditto__body">
      <use xlink:href="#ditto__path"></use>
    </g>
    <g class="ditto__clone" clip-path="url(#ditto__clip)">
      <use xlink:href="#ditto__path" transform="translate(0, -20)"></use>
    </g>
    <g class="ditto__stroke">
      <use xlink:href="#ditto__path"></use>
    </g>
    <g class="ditto__face">
      <g class="ditto__eyes">
        <circle
                class="ditto__eye"
                cx="-35"
                cy="-65"
                r="5"
                fill="#000"
                stroke="none"
        ></circle>
        <circle
                class="ditto__eye"
                cx="35"
                cy="-60"
                r="5"
                fill="#000"
                stroke="none"
        ></circle>
      </g>
      <path
              class="ditto__mouth"
              d="M -40 -35 q -20 -0 80 0"
              stroke-width="4"
              stroke="#000"
              fill="none"
              stroke-linecap="round"
      ></path>
    </g>
  </g>
  <g class="ditto__outline">
    <use xlink:href="#ditto__path"></use>
  </g>
</svg>
<div class="pokeball__wrapper">
  <span class="pokeball__beam"></span>
  <button class="pokeball" title="Change Ditto">
    <span class="pokeball__face"></span>
  </button>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"></script>
<script>
  const {
    gsap: { to, timeline, set },
    d3: { curveBasisClosed, curveStep, lineRadial },
  } = window;

  // Elements
  const DITTO = document.querySelector('.ditto');
  const DITTO_PATH = document.querySelector('.ditto__path');
  const DITTO_MOUTH = document.querySelector('.ditto__mouth');
  const DITTO_BEAM = document.querySelector('.ditto__outline');
  const POKEBALL_BEAM = document.querySelector('.pokeball__beam');
  const DITTO_REAL = document.querySelector('.ditto__real');
  const BUTTON = document.querySelector('button');
  // Configurations
  const CURVES = [curveBasisClosed, curveStep];
  const DITTO_MOUTHS = [
    'M -40 -42.5 q 40 15 80 0', // Smile
    'M -40 -35 q -20 -0 80 0', // Straight
  ];
  // Gonna say that Ditto has around 14 points
  // Linear Radial goes 0 -> 360 CW from 12
  const DEFAULT_POINTS = [
    [0, 100],
    [15, 120],
    [30, 130],
    [50, 95],
    [70, 140],
    [80, 150],
    [90, 130],
    [100, 90],
    [120, 160],
    [130, 170],
    [140, 160],
    [145, 130],
    [180, 150],
    [215, 130],
    [220, 160],
    [230, 180],
    [270, 80],
    [280, 160],
    [300, 190],
    [315, 80],
    [325, 140],
    [345, 160],
  ];

  const SQUARE_POINTS = [
    [45, 150],
    [135, 150],
    [225, 150],
    [315, 150],
  ];

  const SWAY = {
    X: 10,
    Y: 40,
  };

  // Utility function
  const randomInRange = (min, max) =>
          Math.floor(Math.random() * (max - min + 1)) + min;
  // Return new points for Ditto body
  // Maps the default points given some bounds
  const getPoints = () => {
    if (Math.random() > 0.75) return SQUARE_POINTS;
    const POINTS = DEFAULT_POINTS.map((point) => [
      randomInRange(point[0] - SWAY.X, point[0] + SWAY.X),
      randomInRange(point[1] - SWAY.Y, point[1] + SWAY.Y),
    ]);

    return POINTS;
  };
  // Draws an outline for Ditto and determines whether
  // he's smiling or what he color he is
  const drawDitto = (
          points = DEFAULT_POINTS,
          curveBasis = curveBasisClosed,
          hue = 320
  ) => {
    const PATH = lineRadial().curve(curveBasis)(
            points.map((point) => [point[0] * (Math.PI / 180), point[1]])
    );

    DITTO.style.setProperty('--hue', hue);
    DITTO_PATH.setAttribute(
            'd',
            PATH.charAt(PATH.length).toLowerCase() !== 'z' ? `${PATH}z` : PATH
    );
  };

  // Draw ditto initially, doesn't necessarily mean he's showing
  drawDitto();

  const STATE = {
    ACTIVE: false,
    DITTO_OUT: false,
    RAN: false,
  };

  const CONFIG = {
    POKEBALL_SPEED: 0.15,
  };

  set(DITTO_BEAM, { transformOrigin: '50% 50%', scale: 1.05, opacity: 0 });
  set(POKEBALL_BEAM, { transformOrigin: '50% 100%', scaleY: 0 });
  // Event binding
  const onComplete = () => {
    BUTTON.removeAttribute('style');
    STATE.DITTO_OUT = !STATE.DITTO_OUT;
    STATE.ACTIVE = false;
  };
  const onStart = () => {
    STATE.ACTIVE = true;
  };
  BUTTON.addEventListener('click', () => {
    if (STATE.ACTIVE) return;
    if (STATE.RAN && !STATE.DITTO_OUT) {
      drawDitto(
              Math.random() > 0.5 ? DEFAULT_POINTS : getPoints(),
              Math.random() > 0.75 ? CURVES[1] : CURVES[0],
              Math.random() > 0.75 ? 180 : 320
      );
    }
    if (!STATE.RAN) STATE.RAN = true;
    set(BUTTON, {
      '--level': 0,
      transformOrigin: '50% 100%',
      rotateX: -20,
    });
    if (STATE.DITTO_OUT) {
      new timeline({
        onStart,
        onComplete,
      })
              .set(POKEBALL_BEAM, { transformOrigin: '50% 100%', opacity: 1 })
              .to(DITTO_BEAM, { duration: CONFIG.POKEBALL_SPEED, opacity: 1 }, 0)
              .to(
                      POKEBALL_BEAM,
                      { duration: CONFIG.POKEBALL_SPEED, scaleY: 1 },
                      0
              )
              .to(DITTO_REAL, { duration: CONFIG.POKEBALL_SPEED, opacity: 0 }, 0)
              .to(
                      DITTO_BEAM,
                      { duration: CONFIG.POKEBALL_SPEED, opacity: 0 },
                      CONFIG.POKEBALL_SPEED
              )
              .to(
                      POKEBALL_BEAM,
                      { duration: CONFIG.POKEBALL_SPEED, scaleY: 0 },
                      CONFIG.POKEBALL_SPEED
              );
    } else {
      new timeline({
        onStart,
        onComplete,
      })
              .to(
                      POKEBALL_BEAM,
                      { duration: CONFIG.POKEBALL_SPEED, scaleY: 1 },
                      0
              )
              .to(DITTO_BEAM, { duration: CONFIG.POKEBALL_SPEED, opacity: 1 }, 0)
              .to(
                      DITTO_BEAM,
                      { duration: CONFIG.POKEBALL_SPEED, opacity: 0 },
                      CONFIG.POKEBALL_SPEED
              )
              .to(
                      DITTO_REAL,
                      { duration: CONFIG.POKEBALL_SPEED, opacity: 1 },
                      CONFIG.POKEBALL_SPEED
              )
              .to(
                      POKEBALL_BEAM,
                      {
                        duration: CONFIG.POKEBALL_SPEED,
                        opacity: 0,
                        transformOrigin: '50% 0',
                        scaleY: 0,
                      },

                      CONFIG.POKEBALL_SPEED
              );
    }
  });

  // Update his mouth
  const smileOrNoSmile = () => {
    const MOUTH_INDEX = randomInRange(0, DITTO_MOUTHS.length - 1);
    DITTO_MOUTH.setAttribute('d', DITTO_MOUTHS[MOUTH_INDEX]);
    setTimeout(smileOrNoSmile, randomInRange(4000, 10000));
  };
  smileOrNoSmile();

  const EYES = document.querySelector('.ditto__eyes');
  const blink = () => {
    set(EYES, { scaleY: 1 });
    if (EYES.BLINK_TL) EYES.BLINK_TL.kill();
    EYES.BLINK_TL = new timeline({
      delay: Math.floor(Math.random() * 4) + 1,
      onComplete: () => blink(EYES),
    }).to(EYES, {
      duration: 0.05,
      transformOrigin: '50% 50%',
      scaleY: 0,
      yoyo: true,
      repeat: 1,
    });
  };
  blink();
</script>
</body>
</html>
